﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.ComponentModel;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace Ruandao.Misc
{
    /// <summary>
    /// 
    /// </summary>
    public class TypeUtil
    {
        #region 映射对象的属性值。
        /// <summary>
        /// 映射对象的属性值。指定属性对于源对象必须支持get访问，对于目标
        /// 对象必须支持set访问。
        /// </summary>
        /// <param name="source">源对象。</param>
        /// <param name="target">目标对象。</param>
        /// <param name="propertyList">要映射的属性列表,不指定则表示映射所有的源对象属性。</param>
        public static void MapProperty(
            object source, object target,
            params string[] propertyList)
        {
            List<string> properties = new List<string>();
            if( propertyList !=null ) properties = new List<string>(propertyList);
            PropertyInfo[] sourceProperties =
              GetSourceProperties(source.GetType());
            foreach (PropertyInfo sourceProperty in sourceProperties)
            {
                string propertyName = sourceProperty.Name;
                if (properties.Count!=0 && !properties.Contains(propertyName))
                {
                    continue;
                }

                try
                {
                    SetPropertyValue(
                        target, propertyName,
                        sourceProperty.GetValue(source, null));
                }
                catch (Exception ex)
                {
                    throw new ArgumentException(
                        String.Format("{0} ({1})",
                        "PropertyCopyFailed", propertyName), ex);
                }
            }
        }


        private static PropertyInfo[] GetSourceProperties(Type sourceType)
        {
            List<PropertyInfo> result = new List<PropertyInfo>();
            PropertyDescriptorCollection props =
              TypeDescriptor.GetProperties(sourceType);
            foreach (PropertyDescriptor item in props)
                if (item.IsBrowsable)
                    result.Add(sourceType.GetProperty(item.Name));
            return result.ToArray();
        }


        /// <summary>
        /// Sets an object's property with the specified value,
        /// coercing that value to the appropriate type if possible.
        /// </summary>
        /// <param name="target">Object containing the property to set.</param>
        /// <param name="propertyName">Name of the property to set.</param>
        /// <param name="value">Value to set into the property.</param>
        public static void SetPropertyValue(
          object target, string propertyName, object value)
        {
            PropertyInfo propertyInfo =
              target.GetType().GetProperty(propertyName);
            if (value == null)
                propertyInfo.SetValue(target, value, null);
            else
            {
                Type pType =
                  GetPropertyType(propertyInfo.PropertyType);
                Type vType =
                  GetPropertyType(value.GetType());
                if (pType.Equals(vType))
                {
                    // types match, just copy value
                    propertyInfo.SetValue(target, value, null);
                }
                else
                {
                    // types don't match, try to coerce
                    if (pType.Equals(typeof(Guid)))
                        propertyInfo.SetValue(
                          target, new Guid(value.ToString()), null);
                    else if (pType.IsEnum && vType.Equals(typeof(string)))
                        propertyInfo.SetValue(target, Enum.Parse(pType, value.ToString()), null);
                    else
                        propertyInfo.SetValue(
                          target, Convert.ChangeType(value, pType), null);
                }
            }
        }

        /// <summary>
        /// Returns a property's type, dealing with
        /// Nullable(Of T) if necessary.
        /// </summary>
        /// <param name="propertyType">Type of the
        /// property as returned by reflection.</param>
        public static Type GetPropertyType(Type propertyType)
        {
            Type type = propertyType;
            if (type.IsGenericType &&
              (type.GetGenericTypeDefinition() == typeof(Nullable<>)))
                return Nullable.GetUnderlyingType(type);
            return type;
        }
        #endregion

        #region 数据类型和字节数组之间的转换
        /// <summary>
        /// 把整数转换为4字节数组。
        /// </summary>
        /// <param name="value">要转换的整数。</param>
        /// <returns>从低位到高位排列的字节数组。</returns>
        public static byte[] BytesFromInt(int value)
        {
            byte[] bytes = new byte[4];
            bytes[0] = (byte)(value & 0xFF);
            bytes[1] = (byte)((value >> 8) & 0xFF);
            bytes[2] = (byte)((value >> 16) & 0xFF);
            bytes[3] = (byte)((value >> 24) & 0xFF);
            return bytes;
        }

        /// <summary>
        /// 将4字节数组转换为一个整数。
        /// </summary>
        /// <param name="bytes">从低位到高位排列的字节数组。</param>
        /// <returns>整数。</returns>
        public static int IntFromBytes(byte[] bytes)
        {
            int value = 0;
            value = value | (((int)(bytes[0]) & 0xFF));
            value = value | (((int)(bytes[1]) & 0xFF) << 8);
            value = value | (((int)(bytes[2]) & 0xFF) << 16);
            value = value | (((int)(bytes[3]) & 0xFF) << 24);
            return value;
        }

        /// <summary>
        /// 把整数数组转换为字节数组，字节数组中每4个字节代表一个整数。
        /// </summary>
        /// <param name="values">整数数组。</param>
        /// <returns>字节数组。</returns>
        public static byte[] BytesFromIntArray(int[] values)
        {
            byte[] bytes = new byte[values.Length * 4];
            for (int i = 0; i < values.Length; i++)
            {
                bytes[i * 4] = (byte)(values[i] & 0xFF);
                bytes[i * 4 + 1] = (byte)((values[i] >> 8) & 0xFF);
                bytes[i * 4 + 2] = (byte)((values[i] >> 16) & 0xFF);
                bytes[i * 4 + 3] = (byte)((values[i] >> 24) & 0xFF);
            }
            return bytes;
        }

        /// <summary>
        /// 把字节数组转换为整数数组，字节数组中每4个字节代表一个整数。
        /// </summary>
        /// <param name="bytes">字节数组。</param>
        /// <returns>整数数组</returns>
        public static int[] IntAarrayFromBytes(byte[] bytes)
        {
            int count = bytes.Length / 4;
            int[] values = new int[count];
            for (int i = 0; i < count; i++)
            {
                values[i] = values[i] | (((int)(bytes[i * 4]) & 0xFF));
                values[i] = values[i] | (((int)(bytes[i * 4 + 1]) & 0xFF) << 8);
                values[i] = values[i] | (((int)(bytes[i * 4 + 2]) & 0xFF) << 16);
                values[i] = values[i] | (((int)(bytes[i * 4 + 3]) & 0xFF) << 24);
            }
            return values;
        }

        /// <summary>
        /// 把对象序列化为一个字节数组。
        /// </summary>
        /// <param name="data">要序列化的对象。</param>
        /// <returns>字节数组。</returns>
        static public byte[] ObjectToBytes(object data)
        {
            MemoryStream stream = new MemoryStream();
            new BinaryFormatter().Serialize(stream, data);
            stream.Close();
            return stream.ToArray();
        }

        /// <summary>
        /// 把字节数组反序列化成为一个对象。
        /// </summary>
        /// <param name="bytes">字节数组。</param>
        /// <returns>对象。</returns>
        static public object BytesToObject(Byte[] bytes)
        {
            MemoryStream stream = new MemoryStream(bytes);
            return new BinaryFormatter().Deserialize(stream);
        }

        #endregion

        #region 数据类型转换

        /// <summary>
        /// 根据枚举名称得到枚举值，注意C#语法中不支持enum的类型约束。
        /// </summary>
        /// <typeparam name="T">枚举类型</typeparam>
        /// <param name="name">枚举值的名称</param>
        /// <returns>枚举值</returns>
        public static T EnumParse<T>(string name)
        {
            return (T)Enum.Parse(typeof(T), name);
        }

        /// <summary>
        /// 所支持类型包括：
        /// 1) 基本类型：Boolean,Byte,Char,DateTime,Decimal,Double,Int16,Int32,Int64,
        /// SByte,Single,String,UInt16,UInt32,UInt64。
        /// 2）枚举类型
        /// 3）List&lt;value&gt;,value[]。字符串之间用逗号或分号分隔。value为上述基本类型之一。
        /// 3）List&lt;value,value&gt;Dictionary&lt;value,value&gt;。每一项用"{}"标识，value与value之间用逗号或分号分隔。value为上述基本类型
        /// </summary>
        /// <param name="value">被转换的值。</param>
        /// <param name="type">目标类型。</param>
        /// <returns>转换后的值。</returns>
        public static object ConvertTo(object value, Type type)
        {
            object toValue = null;
            switch (type.FullName)
            {
                case "System.Boolean":
                    toValue = Convert.ToBoolean(value);
                    break;
                case "System.Byte":
                    toValue = Convert.ToByte(value);
                    break;
                case "System.Char":
                    toValue = Convert.ToChar(value);
                    break;
                case "System.DateTime":
                    toValue = Convert.ToDateTime(value);
                    break;
                case "System.Decimal":
                    toValue = Convert.ToDecimal(value);
                    break;
                case "System.Double":
                    toValue = Convert.ToDouble(value);
                    break;
                case "System.Int16":
                    toValue = Convert.ToInt16(value);
                    break;
                case "System.Int32":
                    toValue = Convert.ToInt32(value);
                    break;
                case "System.Int64":
                    toValue = Convert.ToInt64(value);
                    break;
                case "System.SByte":
                    toValue = Convert.ToSByte(value);
                    break;
                case "System.Single":
                    toValue = Convert.ToSingle(value);
                    break;
                case "System.String":
                    toValue = Convert.ToString(value);
                    break;
                case "System.UInt16":
                    toValue = Convert.ToUInt16(value);
                    break;
                case "System.UInt32":
                    toValue = Convert.ToUInt32(value);
                    break;
                case "System.UInt64":
                    toValue = Convert.ToUInt64(value);
                    break;
                default:
                    throw new Ruandao.InvalidCodeException("ConvertTo不支持转换的数据类型：" + type.FullName);
            }
            return toValue;

        #endregion

        }
    }
}
